/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.calcite.rex;
import org.apache.calcite.avatica.util.ByteString;
import org.apache.calcite.avatica.util.DateTimeUtils;
import org.apache.calcite.avatica.util.Spaces;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
import org.apache.calcite.rel.type.RelDataTypeImpl;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.ArraySqlType;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.MapSqlType;
import org.apache.calcite.sql.type.MultisetSqlType;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.test.CustomTypeSystems;
import org.apache.calcite.test.RexImplicationCheckerFixtures;
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.TimeString;
import org.apache.calcite.util.TimeWithTimeZoneString;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.TimestampWithTimeZoneString;
import org.apache.calcite.util.Util;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.params.provider.Arguments.of;

/**
 * Test for {@link RexBuilder}.
 */
class RexBuilderTest {

  private static final int PRECISION = 256;

  /**
   * MySqlTypeFactoryImpl provides a specific implementation of
   * {@link SqlTypeFactoryImpl} which sets precision to 256 for VARCHAR.
   */
  private static class MySqlTypeFactoryImpl extends SqlTypeFactoryImpl {

    MySqlTypeFactoryImpl(RelDataTypeSystem typeSystem) {
      super(typeSystem);
    }

    @Override public RelDataType createTypeWithNullability(
        final RelDataType type,
        final boolean nullable) {
      if (type.getSqlTypeName() == SqlTypeName.VARCHAR) {
        return new BasicSqlType(this.typeSystem, type.getSqlTypeName(),
            PRECISION);
      }
      return super.createTypeWithNullability(type, nullable);
    }
  }


  /**
   * Test RexBuilder.ensureType()
   */
  @Test void testEnsureTypeWithAny() {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder builder = new RexBuilder(typeFactory);

    RexNode node =
        new RexLiteral(Boolean.TRUE,
            typeFactory.createSqlType(SqlTypeName.BOOLEAN), SqlTypeName.BOOLEAN);
    RexNode ensuredNode =
        builder.ensureType(typeFactory.createSqlType(SqlTypeName.ANY), node,
            true);

    assertThat(ensuredNode, is(node));
  }

  /**
   * Test RexBuilder.ensureType()
   */
  @Test void testEnsureTypeWithItself() {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder builder = new RexBuilder(typeFactory);

    RexNode node =
        new RexLiteral(Boolean.TRUE,
            typeFactory.createSqlType(SqlTypeName.BOOLEAN), SqlTypeName.BOOLEAN);
    RexNode ensuredNode =
        builder.ensureType(typeFactory.createSqlType(SqlTypeName.BOOLEAN), node,
            true);

    assertThat(ensuredNode, is(node));
  }

  /**
   * Test RexBuilder.ensureType()
   */
  @Test void testEnsureTypeWithDifference() {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder builder = new RexBuilder(typeFactory);

    RexNode node =
        new RexLiteral(Boolean.TRUE,
            typeFactory.createSqlType(SqlTypeName.BOOLEAN), SqlTypeName.BOOLEAN);
    RexNode ensuredNode =
        builder.ensureType(typeFactory.createSqlType(SqlTypeName.INTEGER), node,
            true);

    assertNotEquals(node, ensuredNode);
    assertThat(typeFactory.createSqlType(SqlTypeName.INTEGER),
        is(ensuredNode.getType()));
  }

  private static final long MOON = -14159025000L;

  private static final int MOON_DAY = -164;

  private static final int MOON_TIME = 10575000;

  /** Tests {@link RexBuilder#makeTimestampLiteral(TimestampString, int)}. */
  @Test void testTimestampLiteral() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RelDataType timestampType =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP);
    final RelDataType timestampType3 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP, 3);
    final RelDataType timestampType9 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP, 9);
    final RelDataType timestampType18 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP, 18);
    final RexBuilder builder = new RexBuilder(typeFactory);

    // Old way: provide a Calendar
    final Calendar calendar = Util.calendar();
    calendar.set(1969, Calendar.JULY, 21, 2, 56, 15); // one small step
    calendar.set(Calendar.MILLISECOND, 0);
    checkTimestamp(builder.makeLiteral(calendar, timestampType));

    // Old way #2: Provide a Long
    checkTimestamp(builder.makeLiteral(MOON, timestampType));

    // The new way
    final TimestampString ts = new TimestampString(1969, 7, 21, 2, 56, 15);
    checkTimestamp(builder.makeLiteral(ts, timestampType));

    // Now with milliseconds
    final TimestampString ts2 = ts.withMillis(56);
    assertThat(ts2, hasToString("1969-07-21 02:56:15.056"));
    final RexLiteral literal2 = builder.makeLiteral(ts2, timestampType3);
    assertThat(literal2.getValueAs(TimestampString.class),
        hasToString("1969-07-21 02:56:15.056"));

    // Now with nanoseconds
    final TimestampString ts3 = ts.withNanos(56);
    final RexLiteral literal3 = builder.makeLiteral(ts3, timestampType9);
    assertThat(literal3.getValueAs(TimestampString.class),
        hasToString("1969-07-21 02:56:15"));
    final TimestampString ts3b = ts.withNanos(2345678);
    final RexLiteral literal3b = builder.makeLiteral(ts3b, timestampType9);
    assertThat(literal3b.getValueAs(TimestampString.class),
        hasToString("1969-07-21 02:56:15.002"));

    // Now with a very long fraction
    final TimestampString ts4 = ts.withFraction("102030405060708090102");
    final RexLiteral literal4 = builder.makeLiteral(ts4, timestampType18);
    assertThat(literal4.getValueAs(TimestampString.class),
        hasToString("1969-07-21 02:56:15.102"));
  }

  /** Test cases for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6389">[CALCITE-6389]
   * RexBuilder.removeCastFromLiteral does not preserve semantics for some types of literal</a>. */
  @Test void testRemoveCast() {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder builder = new RexBuilder(typeFactory);

    // Can remove cast of an integer to an integer
    BigDecimal value = new BigDecimal(10);
    RelDataType toType = builder.typeFactory.createSqlType(SqlTypeName.INTEGER);
    assertTrue(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.INTEGER));

    // Can remove cast from integer to decimal
    toType = builder.typeFactory.createSqlType(SqlTypeName.DECIMAL);
    assertTrue(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.INTEGER));

    // 250 is too large for a TINYINT
    value = new BigDecimal(250);
    toType = builder.typeFactory.createSqlType(SqlTypeName.TINYINT);
    assertFalse(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.INTEGER));

    // 50 isn't too large for a TINYINT
    value = new BigDecimal(50);
    toType = builder.typeFactory.createSqlType(SqlTypeName.TINYINT);
    assertTrue(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.INTEGER));

    // 120.25 cannot be represented with precision 2 and scale 2 without loss
    value = new BigDecimal("120.25");
    toType = builder.typeFactory.createSqlType(SqlTypeName.DECIMAL, 2, 2);
    assertFalse(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.DECIMAL));

    // 120.25 cannot be represented with precision 5 and scale 1 without rounding
    value = new BigDecimal("120.25");
    toType = builder.typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 1);
    assertFalse(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.DECIMAL));

    // longmax + 1 cannot be represented as a long
    value = new BigDecimal(Long.MAX_VALUE).add(BigDecimal.ONE);
    toType = builder.typeFactory.createSqlType(SqlTypeName.BIGINT);
    assertFalse(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.DECIMAL));

    // Cast to decimal of an INTERVAL '5' seconds cannot be removed
    value = new BigDecimal("5");
    toType = builder.typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 1);
    assertFalse(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.INTERVAL_SECOND));

    // Cast to decimal of an INTERVAL '5' minutes cannot be removed
    value = new BigDecimal("5");
    toType = builder.typeFactory.createSqlType(SqlTypeName.DECIMAL, 5, 1);
    assertFalse(builder.canRemoveCastFromLiteral(toType, value, SqlTypeName.INTERVAL_MINUTE));
  }

  @Test void testTimestampString() {
    final TimestampString ts = new TimestampString(1969, 7, 21, 2, 56, 15);
    assertThat(ts, hasToString("1969-07-21 02:56:15"));
    assertThat(ts.round(1), is(ts));

    // Now with milliseconds
    final TimestampString ts2 = ts.withMillis(56);
    assertThat(ts2, hasToString("1969-07-21 02:56:15.056"));

    // toString
    assertThat(ts2.round(1), hasToString("1969-07-21 02:56:15"));
    assertThat(ts2.round(2), hasToString("1969-07-21 02:56:15.05"));
    assertThat(ts2.round(3), hasToString("1969-07-21 02:56:15.056"));
    assertThat(ts2.round(4), hasToString("1969-07-21 02:56:15.056"));

    assertThat(ts2.toString(6), is("1969-07-21 02:56:15.056000"));
    assertThat(ts2.toString(1), is("1969-07-21 02:56:15.0"));
    assertThat(ts2.toString(0), is("1969-07-21 02:56:15"));

    assertThat(ts2.round(0), hasToString("1969-07-21 02:56:15"));
    assertThat(ts2.round(0).toString(0), is("1969-07-21 02:56:15"));
    assertThat(ts2.round(0).toString(1), is("1969-07-21 02:56:15.0"));
    assertThat(ts2.round(0).toString(2), is("1969-07-21 02:56:15.00"));

    // Now with milliseconds ending in zero (3 equivalent strings).
    final TimestampString ts3 = ts.withMillis(10);
    assertThat(ts3, hasToString("1969-07-21 02:56:15.01"));

    final TimestampString ts3b = new TimestampString("1969-07-21 02:56:15.01");
    assertThat(ts3b, hasToString("1969-07-21 02:56:15.01"));
    assertThat(ts3b, is(ts3));

    final TimestampString ts3c = new TimestampString("1969-07-21 02:56:15.010");
    assertThat(ts3c, hasToString("1969-07-21 02:56:15.01"));
    assertThat(ts3c, is(ts3));

    // Now with nanoseconds
    final TimestampString ts4 = ts.withNanos(56);
    assertThat(ts4, hasToString("1969-07-21 02:56:15.000000056"));

    // Check rounding; uses RoundingMode.DOWN
    final TimestampString ts5 = ts.withNanos(2345670);
    assertThat(ts5, hasToString("1969-07-21 02:56:15.00234567"));
    assertThat(ts5.round(0), hasToString("1969-07-21 02:56:15"));
    assertThat(ts5.round(1), hasToString("1969-07-21 02:56:15"));
    assertThat(ts5.round(2), hasToString("1969-07-21 02:56:15"));
    assertThat(ts5.round(3), hasToString("1969-07-21 02:56:15.002"));
    assertThat(ts5.round(4), hasToString("1969-07-21 02:56:15.0023"));
    assertThat(ts5.round(5), hasToString("1969-07-21 02:56:15.00234"));
    assertThat(ts5.round(6), hasToString("1969-07-21 02:56:15.002345"));
    assertThat(ts5.round(600), hasToString("1969-07-21 02:56:15.00234567"));

    // Now with a very long fraction
    final TimestampString ts6 = ts.withFraction("102030405060708090102");
    assertThat(ts6, hasToString("1969-07-21 02:56:15.102030405060708090102"));

    // From milliseconds
    final TimestampString ts7 =
        TimestampString.fromMillisSinceEpoch(1456513560123L);
    assertThat(ts7, hasToString("2016-02-26 19:06:00.123"));

    final TimestampString ts8 =
        TimestampString.fromMillisSinceEpoch(1456513560120L);
    assertThat(ts8, hasToString("2016-02-26 19:06:00.12"));

    final TimestampString ts9 = ts8.withFraction("9876543210");
    assertThat(ts9, hasToString("2016-02-26 19:06:00.987654321"));

    // TimestampString.toCalendar
    final Calendar c = ts9.toCalendar();
    assertThat(c.get(Calendar.ERA), is(1)); // CE
    assertThat(c.get(Calendar.YEAR), is(2016));
    assertThat(c.get(Calendar.MONTH), is(1)); // February
    assertThat(c.get(Calendar.DATE), is(26));
    assertThat(c.get(Calendar.HOUR_OF_DAY), is(19));
    assertThat(c.get(Calendar.MINUTE), is(6));
    assertThat(c.get(Calendar.SECOND), is(0));
    assertThat(c.get(Calendar.MILLISECOND), is(987)); // RoundingMode.DOWN
    assertThat(ts9.getMillisSinceEpoch(), is(c.getTimeInMillis()));

    // TimestampString.fromCalendarFields
    c.set(Calendar.YEAR, 1969);
    final TimestampString ts10 = TimestampString.fromCalendarFields(c);
    assertThat(ts10, hasToString("1969-02-26 19:06:00.987"));
    assertThat(ts10.getMillisSinceEpoch(), is(c.getTimeInMillis()));
  }

  @Test void testTimeString() {
    final TimeString t = new TimeString(2, 56, 15);
    assertThat(t, hasToString("02:56:15"));
    assertThat(t.round(1), is(t));

    // Now with milliseconds
    final TimeString t2 = t.withMillis(56);
    assertThat(t2, hasToString("02:56:15.056"));

    // toString
    assertThat(t2.round(1), hasToString("02:56:15"));
    assertThat(t2.round(2), hasToString("02:56:15.05"));
    assertThat(t2.round(3), hasToString("02:56:15.056"));
    assertThat(t2.round(4), hasToString("02:56:15.056"));

    assertThat(t2.toString(6), is("02:56:15.056000"));
    assertThat(t2.toString(1), is("02:56:15.0"));
    assertThat(t2.toString(0), is("02:56:15"));

    assertThat(t2.round(0), hasToString("02:56:15"));
    assertThat(t2.round(0).toString(0), is("02:56:15"));
    assertThat(t2.round(0).toString(1), is("02:56:15.0"));
    assertThat(t2.round(0).toString(2), is("02:56:15.00"));

    // Now with milliseconds ending in zero (3 equivalent strings).
    final TimeString t3 = t.withMillis(10);
    assertThat(t3, hasToString("02:56:15.01"));

    final TimeString t3b = new TimeString("02:56:15.01");
    assertThat(t3b, hasToString("02:56:15.01"));
    assertThat(t3b, is(t3));

    final TimeString t3c = new TimeString("02:56:15.010");
    assertThat(t3c, hasToString("02:56:15.01"));
    assertThat(t3c, is(t3));

    // Now with nanoseconds
    final TimeString t4 = t.withNanos(56);
    assertThat(t4, hasToString("02:56:15.000000056"));

    // Check rounding; uses RoundingMode.DOWN
    final TimeString t5 = t.withNanos(2345670);
    assertThat(t5, hasToString("02:56:15.00234567"));
    assertThat(t5.round(0), hasToString("02:56:15"));
    assertThat(t5.round(1), hasToString("02:56:15"));
    assertThat(t5.round(2), hasToString("02:56:15"));
    assertThat(t5.round(3), hasToString("02:56:15.002"));
    assertThat(t5.round(4), hasToString("02:56:15.0023"));
    assertThat(t5.round(5), hasToString("02:56:15.00234"));
    assertThat(t5.round(6), hasToString("02:56:15.002345"));
    assertThat(t5.round(600), hasToString("02:56:15.00234567"));

    // Now with a very long fraction
    final TimeString t6 = t.withFraction("102030405060708090102");
    assertThat(t6, hasToString("02:56:15.102030405060708090102"));
  }

  private void checkTimestamp(RexLiteral literal) {
    assertThat(literal, hasToString("1969-07-21 02:56:15"));
    assertThat(literal.getValue() instanceof Calendar, is(true));
    assertThat(literal.getValue2() instanceof Long, is(true));
    assertThat(literal.getValue3() instanceof Long, is(true));
    assertThat((Long) literal.getValue2(), is(MOON));
    assertThat(literal.getValueAs(Calendar.class), notNullValue());
    assertThat(literal.getValueAs(TimestampString.class), notNullValue());
  }

  /** Tests
   * {@link RexBuilder#makeTimestampWithLocalTimeZoneLiteral(TimestampString, int)}. */
  @Test void testTimestampWithLocalTimeZoneLiteral() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RelDataType timestampType =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
    final RelDataType timestampType3 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 3);
    final RelDataType timestampType9 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 9);
    final RelDataType timestampType18 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 18);
    final RexBuilder builder = new RexBuilder(typeFactory);

    // The new way
    final TimestampWithTimeZoneString ts =
        new TimestampWithTimeZoneString(1969, 7, 21, 2, 56, 15,
            TimeZone.getTimeZone("PST").getID());
    checkTimestampWithLocalTimeZone(
        builder.makeLiteral(ts.getLocalTimestampString(), timestampType));

    // Now with milliseconds
    final TimestampWithTimeZoneString ts2 = ts.withMillis(56);
    assertThat(ts2, hasToString("1969-07-21 02:56:15.056 PST"));
    final RexLiteral literal2 =
        builder.makeLiteral(ts2.getLocalTimestampString(), timestampType3);
    assertThat(literal2.getValue(), hasToString("1969-07-21 02:56:15.056"));

    // Now with nanoseconds
    final TimestampWithTimeZoneString ts3 = ts.withNanos(56);
    final RexLiteral literal3 =
        builder.makeLiteral(ts3.getLocalTimestampString(), timestampType9);
    assertThat(literal3.getValueAs(TimestampString.class),
        hasToString("1969-07-21 02:56:15"));
    final TimestampWithTimeZoneString ts3b = ts.withNanos(2345678);
    final RexLiteral literal3b =
        builder.makeLiteral(ts3b.getLocalTimestampString(), timestampType9);
    assertThat(literal3b.getValueAs(TimestampString.class),
        hasToString("1969-07-21 02:56:15.002"));

    // Now with a very long fraction
    final TimestampWithTimeZoneString ts4 = ts.withFraction("102030405060708090102");
    final RexLiteral literal4 =
        builder.makeLiteral(ts4.getLocalTimestampString(), timestampType18);
    assertThat(literal4.getValueAs(TimestampString.class),
        hasToString("1969-07-21 02:56:15.102"));

    // toString
    assertThat(ts2.round(1), hasToString("1969-07-21 02:56:15 PST"));
    assertThat(ts2.round(2), hasToString("1969-07-21 02:56:15.05 PST"));
    assertThat(ts2.round(3), hasToString("1969-07-21 02:56:15.056 PST"));
    assertThat(ts2.round(4), hasToString("1969-07-21 02:56:15.056 PST"));

    assertThat(ts2.toString(6), is("1969-07-21 02:56:15.056000 PST"));
    assertThat(ts2.toString(1), is("1969-07-21 02:56:15.0 PST"));
    assertThat(ts2.toString(0), is("1969-07-21 02:56:15 PST"));

    assertThat(ts2.round(0), hasToString("1969-07-21 02:56:15 PST"));
    assertThat(ts2.round(0).toString(0), is("1969-07-21 02:56:15 PST"));
    assertThat(ts2.round(0).toString(1), is("1969-07-21 02:56:15.0 PST"));
    assertThat(ts2.round(0).toString(2), is("1969-07-21 02:56:15.00 PST"));
  }

  private void checkTimestampWithLocalTimeZone(RexLiteral literal) {
    assertThat(literal,
        hasToString("1969-07-21 02:56:15:TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)"));
    assertThat(literal.getValue() instanceof TimestampString, is(true));
    assertThat(literal.getValue2() instanceof Long, is(true));
    assertThat(literal.getValue3() instanceof Long, is(true));
  }

  /** Tests
   * {@link RexBuilder#makeTimestampTzLiteral(TimestampWithTimeZoneString, int)}. */
  @Test void testTimestampTzLiterals() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RelDataType timestampType =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ);
    final RelDataType timestampType3 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ, 3);
    final RelDataType timestampType9 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ, 9);
    final RelDataType timestampType18 =
        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ, 18);
    final RexBuilder builder = new RexBuilder(typeFactory);

    // The new way
    final TimestampWithTimeZoneString ts =
        new TimestampWithTimeZoneString(1969, 7, 21, 2, 56, 15,
            TimeZone.getTimeZone("PST").getID());
    checkTimestampTz(builder.makeLiteral(ts, timestampType));

    // Now with milliseconds
    final TimestampWithTimeZoneString ts2 = ts.withMillis(56);
    assertThat(ts2, hasToString("1969-07-21 02:56:15.056 PST"));
    final RexLiteral literal2 =
        builder.makeLiteral(ts2, timestampType3);
    assertThat(literal2.getValue(), hasToString("1969-07-21 02:56:15.056 PST"));

    // Now with nanoseconds
    final TimestampWithTimeZoneString ts3 = ts.withNanos(56);
    final RexLiteral literal3 =
        builder.makeLiteral(ts3, timestampType9);
    assertThat(literal3.getValueAs(TimestampWithTimeZoneString.class),
        hasToString("1969-07-21 02:56:15 PST"));
    final TimestampWithTimeZoneString ts3b = ts.withNanos(2345678);
    final RexLiteral literal3b =
        builder.makeLiteral(ts3b, timestampType9);
    assertThat(literal3b.getValueAs(TimestampWithTimeZoneString.class),
        hasToString("1969-07-21 02:56:15.002 PST"));

    // Now with a very long fraction
    final TimestampWithTimeZoneString ts4 = ts.withFraction("102030405060708090102");
    final RexLiteral literal4 =
        builder.makeLiteral(ts4, timestampType18);
    assertThat(literal4.getValueAs(TimestampWithTimeZoneString.class),
        hasToString("1969-07-21 02:56:15.102 PST"));

    // toString
    assertThat(ts2.round(1), hasToString("1969-07-21 02:56:15 PST"));
    assertThat(ts2.round(2), hasToString("1969-07-21 02:56:15.05 PST"));
    assertThat(ts2.round(3), hasToString("1969-07-21 02:56:15.056 PST"));
    assertThat(ts2.round(4), hasToString("1969-07-21 02:56:15.056 PST"));

    assertThat(ts2.toString(6), is("1969-07-21 02:56:15.056000 PST"));
    assertThat(ts2.toString(1), is("1969-07-21 02:56:15.0 PST"));
    assertThat(ts2.toString(0), is("1969-07-21 02:56:15 PST"));

    assertThat(ts2.round(0), hasToString("1969-07-21 02:56:15 PST"));
    assertThat(ts2.round(0).toString(0), is("1969-07-21 02:56:15 PST"));
    assertThat(ts2.round(0).toString(1), is("1969-07-21 02:56:15.0 PST"));
    assertThat(ts2.round(0).toString(2), is("1969-07-21 02:56:15.00 PST"));
  }

  private void checkTimestampTz(RexLiteral literal) {
    assertThat(literal,
        hasToString("1969-07-21 02:56:15 PST:TIMESTAMP_TZ(0)"));
    assertThat(literal.getValue() instanceof TimestampWithTimeZoneString, is(true));
    assertThat(literal.getValue2() instanceof Long, is(true));
    assertThat(literal.getValue3() instanceof Long, is(true));
  }

  /** Tests {@link RexBuilder#makeTimeLiteral(TimeString, int)}. */
  @Test void testTimeLiteral() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RelDataType timeType = typeFactory.createSqlType(SqlTypeName.TIME);
    final RelDataType timeType3 =
        typeFactory.createSqlType(SqlTypeName.TIME, 3);
    final RelDataType timeType9 =
        typeFactory.createSqlType(SqlTypeName.TIME, 9);
    final RelDataType timeType18 =
        typeFactory.createSqlType(SqlTypeName.TIME, 18);
    final RexBuilder builder = new RexBuilder(typeFactory);

    // Old way: provide a Calendar
    final Calendar calendar = Util.calendar();
    calendar.set(1969, Calendar.JULY, 21, 2, 56, 15); // one small step
    calendar.set(Calendar.MILLISECOND, 0);
    checkTime(builder.makeLiteral(calendar, timeType));

    // Old way #2: Provide a Long
    checkTime(builder.makeLiteral(MOON_TIME, timeType));

    // The new way
    final TimeString t = new TimeString(2, 56, 15);
    assertThat(t.getMillisOfDay(), is(10575000));
    checkTime(builder.makeLiteral(t, timeType));

    // Now with milliseconds
    final TimeString t2 = t.withMillis(56);
    assertThat(t2.getMillisOfDay(), is(10575056));
    assertThat(t2, hasToString("02:56:15.056"));
    final RexLiteral literal2 = builder.makeLiteral(t2, timeType3);
    assertThat(literal2.getValueAs(TimeString.class),
        hasToString("02:56:15.056"));

    // Now with nanoseconds
    final TimeString t3 = t.withNanos(2345678);
    assertThat(t3.getMillisOfDay(), is(10575002));
    final RexLiteral literal3 = builder.makeLiteral(t3, timeType9);
    assertThat(literal3.getValueAs(TimeString.class),
        hasToString("02:56:15.002"));

    // Now with a very long fraction
    final TimeString t4 = t.withFraction("102030405060708090102");
    assertThat(t4.getMillisOfDay(), is(10575102));
    final RexLiteral literal4 = builder.makeLiteral(t4, timeType18);
    assertThat(literal4.getValueAs(TimeString.class),
        hasToString("02:56:15.102"));

    // toString
    assertThat(t2.round(1), hasToString("02:56:15"));
    assertThat(t2.round(2), hasToString("02:56:15.05"));
    assertThat(t2.round(3), hasToString("02:56:15.056"));
    assertThat(t2.round(4), hasToString("02:56:15.056"));

    assertThat(t2.toString(6), is("02:56:15.056000"));
    assertThat(t2.toString(1), is("02:56:15.0"));
    assertThat(t2.toString(0), is("02:56:15"));

    assertThat(t2.round(0), hasToString("02:56:15"));
    assertThat(t2.round(0).toString(0), is("02:56:15"));
    assertThat(t2.round(0).toString(1), is("02:56:15.0"));
    assertThat(t2.round(0).toString(2), is("02:56:15.00"));

    assertThat(TimeString.fromMillisOfDay(53560123),
        hasToString("14:52:40.123"));
  }

  private void checkTime(RexLiteral literal) {
    assertThat(literal, hasToString("02:56:15"));
    assertThat(literal.getValue() instanceof Calendar, is(true));
    assertThat(literal.getValue2() instanceof Integer, is(true));
    assertThat(literal.getValue3() instanceof Integer, is(true));
    assertThat((Integer) literal.getValue2(), is(MOON_TIME));
    assertThat(literal.getValueAs(Calendar.class), notNullValue());
    assertThat(literal.getValueAs(TimeString.class), notNullValue());
  }

  /** Tests {@link RexBuilder#makeDateLiteral(DateString)}. */
  @Test void testDateLiteral() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RelDataType dateType = typeFactory.createSqlType(SqlTypeName.DATE);
    final RexBuilder builder = new RexBuilder(typeFactory);

    // Old way: provide a Calendar
    final Calendar calendar = Util.calendar();
    calendar.set(1969, Calendar.JULY, 21); // one small step
    calendar.set(Calendar.MILLISECOND, 0);
    checkDate(builder.makeLiteral(calendar, dateType));

    // Old way #2: Provide in Integer
    checkDate(builder.makeLiteral(MOON_DAY, dateType));

    // The new way
    final DateString d = new DateString(1969, 7, 21);
    checkDate(builder.makeLiteral(d, dateType));
  }

  private void checkDate(RexLiteral literal) {
    assertThat(literal, hasToString("1969-07-21"));
    assertThat(literal.getValue() instanceof Calendar, is(true));
    assertThat(literal.getValue2() instanceof Integer, is(true));
    assertThat(literal.getValue3() instanceof Integer, is(true));
    assertThat((Integer) literal.getValue2(), is(MOON_DAY));
    assertThat(literal.getValueAs(Calendar.class), notNullValue());
    assertThat(literal.getValueAs(DateString.class), notNullValue());
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2306">[CALCITE-2306]
   * AssertionError in {@link RexLiteral#getValue3} with null literal of type
   * DECIMAL</a>. */
  @Test void testDecimalLiteral() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RelDataType type = typeFactory.createSqlType(SqlTypeName.DECIMAL);
    final RexBuilder builder = new RexBuilder(typeFactory);
    final RexLiteral literal = builder.makeExactLiteral(null, type);
    assertThat(literal.getValue3(), nullValue());
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3587">[CALCITE-3587]
   * RexBuilder may lose decimal fraction for creating literal with DECIMAL type</a>.
   */
  @Test void testDecimal() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RelDataType type = typeFactory.createSqlType(SqlTypeName.DECIMAL, 4, 2);
    final RexBuilder builder = new RexBuilder(typeFactory);
    try {
      builder.makeLiteral(12.3, type);
      fail();
    } catch (AssertionError e) {
      assertThat(e.getMessage(),
          is("java.lang.Double is not compatible with DECIMAL, try to use makeExactLiteral"));
    }
  }

  /** Tests {@link RexBuilder#makeExactLiteral(BigDecimal, RelDataType)}. */
  @Test void testDecimalWithRoundingMode() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RelDataType type = typeFactory.createSqlType(SqlTypeName.DECIMAL, 4, 2);
    final RexBuilder builder = new RexBuilder(typeFactory);
    RexLiteral rexLiteral = builder.makeExactLiteral(new BigDecimal("13.556"), type);
    assertThat(rexLiteral.getValue() instanceof BigDecimal, is(true));
    assertThat(rexLiteral.getValue(), hasToString("13.55"));
    final RelDataTypeFactory typeFactoryHalfUp =
        new SqlTypeFactoryImpl(new RelDataTypeSystemImpl() {
          @Override public RoundingMode roundingMode() {
            return RoundingMode.HALF_UP;
          }
        });
    final RelDataType typeHalfUp =
        typeFactoryHalfUp.createSqlType(SqlTypeName.DECIMAL, 4, 2);
    final RexBuilder builderHalfUp = new RexBuilder(typeFactoryHalfUp);
    RexLiteral rexLiteralHalfUp =
        builderHalfUp.makeExactLiteral(new BigDecimal("13.556"), typeHalfUp);
    assertThat(rexLiteralHalfUp.getValue() instanceof BigDecimal, is(true));
    assertThat(rexLiteralHalfUp.getValue(), hasToString("13.56"));
  }

  @Test void testDecimalWithNegativeScaleRoundingHalfUp() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(new RelDataTypeSystemImpl() {
          @Override public int getMinScale(SqlTypeName typeName) {
            switch (typeName) {
            case DECIMAL:
              return -2;
            default:
              return super.getMinScale(typeName);
            }
          }

          @Override public RoundingMode roundingMode() {
            return RoundingMode.HALF_UP;
          }
        });
    final RelDataType type = typeFactory.createSqlType(SqlTypeName.DECIMAL, 3, -2);
    final RexBuilder builder = new RexBuilder(typeFactory);
    RexLiteral rexLiteral = builder.makeLiteral(new BigDecimal("12355"), type);
    assertThat(rexLiteral.getValue() instanceof BigDecimal, is(true));
    assertThat(rexLiteral.getValue(), hasToString("12400"));
  }

  @Test void testDecimalWithNegativeScaleRoundingDown() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(
            CustomTypeSystems.withMinScale(RelDataTypeSystem.DEFAULT,
                typeName -> -2));
    final RelDataType type = typeFactory.createSqlType(SqlTypeName.DECIMAL, 3, -2);
    final RexBuilder builder = new RexBuilder(typeFactory);
    RexLiteral rexLiteralHalfUp = builder.makeLiteral(new BigDecimal("12355"), type);
    assertThat(rexLiteralHalfUp.getValue() instanceof BigDecimal, is(true));
    assertThat(rexLiteralHalfUp.getValue(), hasToString("12300"));
  }

  /** Tests {@link DateString} year range. */
  @Test void testDateStringYearError() {
    try {
      final DateString dateString = new DateString(11969, 7, 21);
      fail("expected exception, got " + dateString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Year out of range: [11969]"));
    }
    try {
      final DateString dateString = new DateString("12345-01-23");
      fail("expected exception, got " + dateString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(),
          containsString("Invalid date format: [12345-01-23]"));
    }
  }

  /** Tests {@link DateString} month range. */
  @Test void testDateStringMonthError() {
    try {
      final DateString dateString = new DateString(1969, 27, 21);
      fail("expected exception, got " + dateString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Month out of range: [27]"));
    }
    try {
      final DateString dateString = new DateString("1234-13-02");
      fail("expected exception, got " + dateString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Month out of range: [13]"));
    }
  }

  /** Tests {@link DateString} day range. */
  @Test void testDateStringDayError() {
    try {
      final DateString dateString = new DateString(1969, 7, 41);
      fail("expected exception, got " + dateString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Day out of range: [41]"));
    }
    try {
      final DateString dateString = new DateString("1234-01-32");
      fail("expected exception, got " + dateString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Day out of range: [32]"));
    }
    // We don't worry about the number of days in a month. 30 is in range.
    final DateString dateString = new DateString("1234-02-30");
    assertThat(dateString, notNullValue());
  }

  /** Tests {@link TimeString} hour range. */
  @Test void testTimeStringHourError() {
    try {
      final TimeString timeString = new TimeString(111, 34, 56);
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Hour out of range: [111]"));
    }
    try {
      final TimeString timeString = new TimeString("24:00:00");
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Hour out of range: [24]"));
    }
    try {
      final TimeString timeString = new TimeString("24:00");
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(),
          containsString("Invalid time format: [24:00]"));
    }
  }

  /** Tests {@link TimeString} minute range. */
  @Test void testTimeStringMinuteError() {
    try {
      final TimeString timeString = new TimeString(12, 334, 56);
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Minute out of range: [334]"));
    }
    try {
      final TimeString timeString = new TimeString("12:60:23");
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Minute out of range: [60]"));
    }
  }

  /** Tests {@link TimeString} second range. */
  @Test void testTimeStringSecondError() {
    try {
      final TimeString timeString = new TimeString(12, 34, 567);
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Second out of range: [567]"));
    }
    try {
      final TimeString timeString = new TimeString(12, 34, -4);
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Second out of range: [-4]"));
    }
    try {
      final TimeString timeString = new TimeString("12:34:60");
      fail("expected exception, got " + timeString);
    } catch (IllegalArgumentException e) {
      assertThat(e.getMessage(), containsString("Second out of range: [60]"));
    }
  }

  /**
   * Test string literal encoding.
   */
  @Test void testStringLiteral() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RelDataType varchar =
        typeFactory.createSqlType(SqlTypeName.VARCHAR);
    final RexBuilder builder = new RexBuilder(typeFactory);

    final NlsString latin1 = new NlsString("foobar", "LATIN1", SqlCollation.IMPLICIT);
    final NlsString utf8 = new NlsString("foobar", "UTF8", SqlCollation.IMPLICIT);

    RexLiteral literal = builder.makePreciseStringLiteral("foobar");
    assertThat(literal, hasToString("'foobar'"));
    literal =
        builder.makePreciseStringLiteral(
            new ByteString(new byte[] { 'f', 'o', 'o', 'b', 'a', 'r'}),
            "UTF8", SqlCollation.IMPLICIT);
    assertThat(literal, hasToString("_UTF8'foobar'"));
    assertThat(literal.computeDigest(RexDigestIncludeType.ALWAYS),
        is("_UTF8'foobar':CHAR(6) CHARACTER SET \"UTF-8\""));
    literal =
        builder.makePreciseStringLiteral(
            new ByteString("\u82f1\u56fd".getBytes(StandardCharsets.UTF_8)),
            "UTF8", SqlCollation.IMPLICIT);
    assertThat(literal, hasToString("_UTF8'\u82f1\u56fd'"));
    // Test again to check decode cache.
    literal =
        builder.makePreciseStringLiteral(
            new ByteString("\u82f1".getBytes(StandardCharsets.UTF_8)),
            "UTF8", SqlCollation.IMPLICIT);
    assertThat(literal, hasToString("_UTF8'\u82f1'"));
    try {
      literal =
          builder.makePreciseStringLiteral(
              new ByteString("\u82f1\u56fd".getBytes(StandardCharsets.UTF_8)),
              "GB2312", SqlCollation.IMPLICIT);
      fail("expected exception, got " + literal);
    } catch (RuntimeException e) {
      assertThat(e.getMessage(), containsString("Failed to encode"));
    }
    literal = builder.makeLiteral(latin1, varchar);
    assertThat(literal, hasToString("_LATIN1'foobar'"));
    literal = builder.makeLiteral(utf8, varchar);
    assertThat(literal, hasToString("_UTF8'foobar'"));
  }

  /** Tests {@link RexBuilder#makeExactLiteral(java.math.BigDecimal)}. */
  @Test void testBigDecimalLiteral() {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(new RelDataTypeSystemImpl() {
      @Override public int getMaxPrecision(SqlTypeName typeName) {
        return 38;
      }
    });
    final RexBuilder builder = new RexBuilder(typeFactory);
    checkBigDecimalLiteral(builder, "25");
    checkBigDecimalLiteral(builder, "9.9");
    checkBigDecimalLiteral(builder, "0");
    checkBigDecimalLiteral(builder, "-75.5");
    checkBigDecimalLiteral(builder, "10000000");
    checkBigDecimalLiteral(builder, "100000.111111111111111111");
    checkBigDecimalLiteral(builder, "-100000.111111111111111111");
    checkBigDecimalLiteral(builder, "73786976294838206464"); // 2^66
    checkBigDecimalLiteral(builder, "-73786976294838206464");
  }

  @Test void testMakeIn() {
    final RelDataTypeFactory typeFactory =
            new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
    final RelDataType floatType = typeFactory.createSqlType(SqlTypeName.REAL);
    RexNode left = rexBuilder.makeInputRef(floatType, 0);
    final RexNode literal1 = rexBuilder.makeLiteral(1.0f, floatType);
    final RexNode literal2 = rexBuilder.makeLiteral(2.0f, floatType);
    RexNode inCall = rexBuilder.makeIn(left, ImmutableList.of(literal1, literal2));
    assertThat(inCall.getKind(), is(SqlKind.SEARCH));
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6989">[CALCITE-6989]
   * Enhance RexBuilder#makeIn to create SEARCH for ARRAY literals</a>.
   */
  @Test void testMakeInReturnsSearchForArrayLiterals() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder rexBuilder = new RexBuilder(typeFactory);
    RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RelDataType arrayIntType = typeFactory.createArrayType(intType, -1);
    RexNode column = rexBuilder.makeInputRef(arrayIntType, 0);
    RexNode l1 = rexBuilder.makeLiteral(ImmutableList.of(100, 200), arrayIntType, false);
    RexNode l2 = rexBuilder.makeLiteral(ImmutableList.of(300, 400), arrayIntType, false);
    RexNode inCall = rexBuilder.makeIn(column, ImmutableList.of(l1, l2));
    assertThat(
        inCall, hasToString("SEARCH($0, Sarg["
        + "[100:INTEGER, 200:INTEGER]:INTEGER NOT NULL ARRAY, "
        + "[300:INTEGER, 400:INTEGER]:INTEGER NOT NULL ARRAY"
        + "]:INTEGER NOT NULL ARRAY)"));
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6608">[CALCITE-6608]
   * RexBuilder#makeIn should create EQUALS instead of SEARCH for single point values</a>.
   */
  @Test void testMakeInReturnsEqualsForSingleLiteral() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder rexBuilder = new RexBuilder(typeFactory);
    RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RexNode column = rexBuilder.makeInputRef(intType, 0);
    RexLiteral literal = rexBuilder.makeLiteral(100, intType);
    RexNode inCall = rexBuilder.makeIn(column, ImmutableList.of(literal));
    assertThat(inCall, hasToString("=($0, 100)"));
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6608">[CALCITE-6608]
   * RexBuilder#makeIn should create EQUALS instead of SEARCH for single point values</a>.
   */
  @Test void testMakeInReturnsEqualsForDuplicateLiterals() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder rexBuilder = new RexBuilder(typeFactory);
    RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RexNode column = rexBuilder.makeInputRef(intType, 0);
    RexLiteral literal = rexBuilder.makeLiteral(100, intType);
    RexNode inCall = rexBuilder.makeIn(column, ImmutableList.of(literal, literal));
    assertThat(inCall, hasToString("=($0, 100)"));
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6608">[CALCITE-6608]
   * RexBuilder#makeIn should create EQUALS instead of SEARCH for single point values</a>.
   */
  @Test void testMakeInReturnsEqualsForSingleExpression() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder rexBuilder = new RexBuilder(typeFactory);
    RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RexNode column0 = rexBuilder.makeInputRef(intType, 0);
    RexNode plusCall =
        rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
            rexBuilder.makeInputRef(intType, 1),
            rexBuilder.makeInputRef(intType, 2));
    RexNode inCall = rexBuilder.makeIn(column0, ImmutableList.of(plusCall));
    assertThat(inCall, hasToString("=($0, +($1, $2))"));
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6608">[CALCITE-6608]
   * RexBuilder#makeIn should create EQUALS instead of SEARCH for single point values</a>.
   */
  @Test void testMakeInReturnsEqualsForDuplicateExpressions() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder rexBuilder = new RexBuilder(typeFactory);
    RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RexNode column0 = rexBuilder.makeInputRef(intType, 0);
    RexNode plusCall =
        rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
            rexBuilder.makeInputRef(intType, 1),
            rexBuilder.makeInputRef(intType, 2));
    RexNode inCall = rexBuilder.makeIn(column0, ImmutableList.of(plusCall, plusCall));
    assertThat(inCall, hasToString("=($0, +($1, $2))"));
  }

  @Test void testMakeInReturnsOrForMultipleExpressions() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder rexBuilder = new RexBuilder(typeFactory);
    RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RexNode column0 = rexBuilder.makeInputRef(intType, 0);
    RexNode plusCall =
        rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
            rexBuilder.makeInputRef(intType, 1),
            rexBuilder.makeInputRef(intType, 2));
    RexNode minusCall =
        rexBuilder.makeCall(SqlStdOperatorTable.MINUS,
            rexBuilder.makeInputRef(intType, 1),
            rexBuilder.makeInputRef(intType, 2));
    RexNode inCall = rexBuilder.makeIn(column0, ImmutableList.of(plusCall, minusCall));
    assertThat(inCall, hasToString("OR(=($0, +($1, $2)), =($0, -($1, $2)))"));
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4555">[CALCITE-4555]
   * Invalid zero literal value is used for
   * TIMESTAMP WITH LOCAL TIME ZONE type in RexBuilder</a>. */
  @ParameterizedTest
  @MethodSource("testData4testMakeZeroLiteral")
  void testMakeZeroLiteral(RelDataType type, RexLiteral expected) {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
    assertThat(rexBuilder.makeZeroLiteral(type), is(equalTo(expected)));
  }

  private static Stream<Arguments> testData4testMakeZeroLiteral() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
    BiFunction<RelDataType, Function<RelDataType, Comparable>, Arguments> type2rexLiteral =
        (relDataType, relDataTypeComparableFunction) ->
            of(relDataType,
                rexBuilder.makeLiteral(
                    relDataTypeComparableFunction.apply(relDataType), relDataType));
    return Stream.of(
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.CHAR),
            relDataType -> new NlsString(Spaces.of(relDataType.getPrecision()), null, null)),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.VARCHAR),
            relDataType -> new NlsString("", null, null)),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.BINARY),
            relDataType -> new ByteString(new byte[relDataType.getPrecision()])),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.VARBINARY),
            relDataType -> ByteString.EMPTY),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.TINYINT),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.SMALLINT),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.INTEGER),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.BIGINT),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.DECIMAL),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.FLOAT),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.REAL),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.DOUBLE),
            relDataType -> BigDecimal.ZERO),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.BOOLEAN),
            relDataType -> false),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.TIME),
            relDataType -> DateTimeUtils.ZERO_CALENDAR),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.DATE),
            relDataType -> DateTimeUtils.ZERO_CALENDAR),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.TIMESTAMP),
            relDataType -> DateTimeUtils.ZERO_CALENDAR),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE),
            relDataType -> new TimeString(0, 0, 0)),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE),
            relDataType -> new TimestampString(1, 1, 1, 0, 0, 0)),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.TIME_TZ),
            relDataType -> new TimeWithTimeZoneString(0, 0, 0, "GMT+00:00")),
        type2rexLiteral.apply(typeFactory.createSqlType(SqlTypeName.TIMESTAMP_TZ),
            relDataType -> new TimestampWithTimeZoneString(1, 1, 1, 0, 0, 0, "GMT+00:00")));
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6938">[CALCITE-6938]
   * Support zero value creation of nested data types</a>. */
  @ParameterizedTest
  @MethodSource("testData4testMakeZeroForNestedType")
  void testMakeZeroForNestedType(RelDataType type, RexNode expected) {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
    assertThat(rexBuilder.makeZeroRexNode(type), is(equalTo(expected)));
  }

  @Test void testCreateCoalesce() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder b = new RexBuilder(typeFactory);
    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);

    RelDataType arrayType = new ArraySqlType(varcharType, false);
    RexNode arrayZero = b.makeZeroRexNode(arrayType);

    RexNode array =
        b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
        ImmutableList.of(
            b.makeLiteral("1", varcharType)));

    RexNode coalesce1 = b.makeCall(SqlStdOperatorTable.COALESCE, array, arrayZero);
    assertThat(
        coalesce1, hasToString(
        "COALESCE(ARRAY('1'), CAST(ARRAY()):VARCHAR NOT NULL ARRAY NOT NULL)"));

    RelDataType mapType = new MapSqlType(arrayType, arrayType, true);
    RexNode mapZero = b.makeZeroRexNode(mapType);

    RexNode map =
        b.makeCall(new MapSqlType(arrayType, arrayType, true),
        SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
        ImmutableList.of(array, array));

    RexNode coalesce2 = b.makeCall(SqlStdOperatorTable.COALESCE, map, mapZero);
    assertThat(
        coalesce2, hasToString(
        "COALESCE(MAP(ARRAY('1'), ARRAY('1')), "
        + "CAST(MAP()):(VARCHAR NOT NULL ARRAY NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP)"));
  }

  private static Stream<Arguments> testData4testMakeZeroForNestedType() {
    RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RexBuilder b = new RexBuilder(typeFactory);

    RelDataType integerType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RelDataType varcharType = typeFactory.createSqlType(SqlTypeName.VARCHAR);

    // ARRAY<INTEGER>
    RelDataType arrayType = new ArraySqlType(integerType, false);
    RexNode expectedArray =
        b.makeCast(
            arrayType, b.makeCall(arrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
            ImmutableList.of()));

    // MULTISET<INTEGER>
    RelDataType multisetType = new MultisetSqlType(integerType, false);
    RexNode expectedMultiset =
        b.makeCast(
            multisetType, b.makeCall(multisetType, SqlStdOperatorTable.MULTISET_VALUE,
            ImmutableList.of()));

    // MAP<VARCHAR, INTEGER>
    RelDataType mapType = new MapSqlType(varcharType, integerType, false);
    RexNode expectedMap =
        b.makeCast(
            mapType, b.makeCall(mapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
            ImmutableList.of()));

    // ROW<INTEGER, VARCHAR>
    RelDataType rowType =
        typeFactory.createStructType(
            ImmutableList.of(
            new RelDataTypeFieldImpl("integer", 0, integerType),
            new RelDataTypeFieldImpl("varchar", 1, varcharType)));
    RexNode expectedRow =
        b.makeCall(rowType, SqlStdOperatorTable.ROW,
            ImmutableList.of(b.makeZeroLiteral(integerType),
                b.makeZeroLiteral(varcharType)));

    // ARRAY<ARRAY<INTEGER>>
    RelDataType arrayArrayType = new ArraySqlType(arrayType, false);
    RexNode expectedArrayArray =
        b.makeCast(
            arrayArrayType, b.makeCall(arrayArrayType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
            ImmutableList.of()));

    // ARRAY<MAP<VARCHAR, INTEGER>>
    RelDataType arrayMapType = new ArraySqlType(mapType, false);
    RexNode expectedArrayMap =
        b.makeCast(
            arrayMapType, b.makeCall(arrayMapType, SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR,
            ImmutableList.of()));

    // MAP<MAP<INTEGER, INTEGER>
    RelDataType mapMapType = new MapSqlType(mapType, integerType, false);
    RexNode expectedMapMap =
        b.makeCast(
            mapMapType, b.makeCall(mapMapType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
            ImmutableList.of()));

    // MAP<ARRAY<INTEGER>, INTEGER>
    RelDataType mapArrayType = new MapSqlType(arrayType, integerType, false);
    RexNode expectedMapArray =
        b.makeCast(
            mapArrayType, b.makeCall(mapArrayType, SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR,
            ImmutableList.of()));

    // ROW<ARRAY<INTEGER>, VARCHAR>
    RelDataType rowArrayType =
        typeFactory.createStructType(
            ImmutableList.of(
                new RelDataTypeFieldImpl("array", 0, arrayType),
                new RelDataTypeFieldImpl("varchar", 1, varcharType)));
    RexNode expectedRowArray =
        b.makeCall(rowArrayType, SqlStdOperatorTable.ROW,
            ImmutableList.of(expectedArray,
                b.makeZeroLiteral(varcharType)));

    return Stream.of(
        Arguments.of(arrayType, expectedArray),
        Arguments.of(multisetType, expectedMultiset),
        Arguments.of(mapType, expectedMap),
        Arguments.of(rowType, expectedRow),
        Arguments.of(arrayArrayType, expectedArrayArray),
        Arguments.of(arrayMapType, expectedArrayMap),
        Arguments.of(mapMapType, expectedMapMap),
        Arguments.of(mapArrayType, expectedMapArray),
        Arguments.of(rowArrayType, expectedRowArray));
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4632">[CALCITE-4632]
   * Find the least restrictive datatype for SARG</a>. */
  @Test void testLeastRestrictiveTypeForSargMakeIn() {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
    final RelDataType decimalType = typeFactory.createSqlType(SqlTypeName.DECIMAL);
    RexNode left = rexBuilder.makeInputRef(decimalType, 0);
    final RexNode literal1 = rexBuilder.makeExactLiteral(new BigDecimal("1.0"));
    final RexNode literal2 = rexBuilder.makeExactLiteral(new BigDecimal("20000.0"));

    RexNode inCall = rexBuilder.makeIn(left, ImmutableList.of(literal1, literal2));
    assertThat(inCall.getKind(), is(SqlKind.SEARCH));

    final RexNode sarg = ((RexCall) inCall).operands.get(1);
    RelDataType expected = typeFactory.createSqlType(SqlTypeName.DECIMAL, 6, 1);
    assertThat(expected, is(sarg.getType()));
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4632">[CALCITE-4632]
   * Find the least restrictive datatype for SARG</a>. */
  @Test void testLeastRestrictiveTypeForSargMakeBetween() {
    final RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
    final RelDataType decimalType = typeFactory.createSqlType(SqlTypeName.DECIMAL);
    RexNode left = rexBuilder.makeInputRef(decimalType, 0);
    final RexNode literal1 = rexBuilder.makeExactLiteral(new BigDecimal("1.0"));
    final RexNode literal2 = rexBuilder.makeExactLiteral(new BigDecimal("20000.0"));

    RexNode betweenCall = rexBuilder.makeBetween(left, literal1, literal2);
    assertThat(betweenCall.getKind(), is(SqlKind.SEARCH));

    final RexNode sarg = ((RexCall) betweenCall).operands.get(1);
    RelDataType expected = typeFactory.createSqlType(SqlTypeName.DECIMAL, 6, 1);
    assertThat(expected, is(sarg.getType()));
  }

  /** Tests {@link RexCopier#visitOver(RexOver)}. */
  @Test void testCopyOver() {
    final RelDataTypeFactory sourceTypeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RelDataType type = sourceTypeFactory.createSqlType(SqlTypeName.VARCHAR, 65536);

    final RelDataTypeFactory targetTypeFactory =
        new MySqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder builder = new RexBuilder(targetTypeFactory);

    final RexOver node =
        (RexOver) builder.makeOver(type, SqlStdOperatorTable.COUNT,
            ImmutableList.of(builder.makeInputRef(type, 0)),
            ImmutableList.of(builder.makeInputRef(type, 1)),
            ImmutableList.of(
                new RexFieldCollation(builder.makeInputRef(type, 2),
                    ImmutableSet.of())),
            RexWindowBounds.UNBOUNDED_PRECEDING,
            RexWindowBounds.CURRENT_ROW,
            true, true, false, false, false);
    final RexNode copy = builder.copy(node);
    assertThat(copy, instanceOf(RexOver.class));

    RexOver result = (RexOver) copy;
    assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.VARCHAR));
    assertThat(result.getType().getPrecision(), is(PRECISION));
    assertThat(result.getWindow(), is(node.getWindow()));
    assertThat(result.getAggOperator(), is(node.getAggOperator()));
    assertThat(result.getAggOperator(), is(node.getAggOperator()));
    assertThat(result.isDistinct(), is(node.isDistinct()));
    assertThat(result.ignoreNulls(), is(node.ignoreNulls()));
    for (int i = 0; i < node.getOperands().size(); i++) {
      assertThat(result.getOperands().get(i).getType().getSqlTypeName(),
          is(node.getOperands().get(i).getType().getSqlTypeName()));
      assertThat(result.getOperands().get(i).getType().getPrecision(),
          is(PRECISION));
    }
  }

  /** Tests {@link RexCopier#visitCorrelVariable(RexCorrelVariable)}. */
  @Test void testCopyCorrelVariable() {
    final RelDataTypeFactory sourceTypeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RelDataType type = sourceTypeFactory.createSqlType(SqlTypeName.VARCHAR, 65536);

    final RelDataTypeFactory targetTypeFactory =
        new MySqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder builder = new RexBuilder(targetTypeFactory);

    final RexCorrelVariable node =
        (RexCorrelVariable) builder.makeCorrel(type, new CorrelationId(0));
    final RexNode copy = builder.copy(node);
    assertThat(copy, instanceOf(RexCorrelVariable.class));

    final RexCorrelVariable result = (RexCorrelVariable) copy;
    assertThat(result.id, is(node.id));
    assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.VARCHAR));
    assertThat(result.getType().getPrecision(), is(PRECISION));
  }

  /** Tests {@link RexCopier#visitLocalRef(RexLocalRef)}. */
  @Test void testCopyLocalRef() {
    final RelDataTypeFactory sourceTypeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RelDataType type = sourceTypeFactory.createSqlType(SqlTypeName.VARCHAR, 65536);

    final RelDataTypeFactory targetTypeFactory =
        new MySqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder builder = new RexBuilder(targetTypeFactory);

    final RexLocalRef node = new RexLocalRef(0, type);
    final RexNode copy = builder.copy(node);
    assertThat(copy, instanceOf(RexLocalRef.class));

    final RexLocalRef result = (RexLocalRef) copy;
    assertThat(result.getIndex(), is(node.getIndex()));
    assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.VARCHAR));
    assertThat(result.getType().getPrecision(), is(PRECISION));
  }

  /** Tests {@link RexCopier#visitDynamicParam(RexDynamicParam)}. */
  @Test void testCopyDynamicParam() {
    final RelDataTypeFactory sourceTypeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RelDataType type = sourceTypeFactory.createSqlType(SqlTypeName.VARCHAR, 65536);

    final RelDataTypeFactory targetTypeFactory =
        new MySqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder builder = new RexBuilder(targetTypeFactory);

    final RexDynamicParam node = builder.makeDynamicParam(type, 0);
    final RexNode copy = builder.copy(node);
    assertThat(copy, instanceOf(RexDynamicParam.class));

    final RexDynamicParam result = (RexDynamicParam) copy;
    assertThat(result.getIndex(), is(node.getIndex()));
    assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.VARCHAR));
    assertThat(result.getType().getPrecision(), is(PRECISION));
  }

  /** Tests {@link RexCopier#visitRangeRef(RexRangeRef)}. */
  @Test void testCopyRangeRef() {
    final RelDataTypeFactory sourceTypeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    RelDataType type = sourceTypeFactory.createSqlType(SqlTypeName.VARCHAR, 65536);

    final RelDataTypeFactory targetTypeFactory =
        new MySqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder builder = new RexBuilder(targetTypeFactory);

    final RexRangeRef node = builder.makeRangeReference(type, 1, true);
    final RexNode copy = builder.copy(node);
    assertThat(copy, instanceOf(RexRangeRef.class));

    final RexRangeRef result = (RexRangeRef) copy;
    assertThat(result.getOffset(), is(node.getOffset()));
    assertThat(result.getType().getSqlTypeName(), is(SqlTypeName.VARCHAR));
    assertThat(result.getType().getPrecision(), is(PRECISION));
  }

  private void checkBigDecimalLiteral(RexBuilder builder, String val) {
    final RexLiteral literal = builder.makeExactLiteral(new BigDecimal(val));
    assertThat("builder.makeExactLiteral(new BigDecimal(" + val
            + ")).getValueAs(BigDecimal.class).toString()",
        literal.getValueAs(BigDecimal.class), hasToString(val));
  }

  @Test void testValidateRexFieldAccess() {
    final RelDataTypeFactory typeFactory =
        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    final RexBuilder builder = new RexBuilder(typeFactory);

    RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
    RelDataType longType = typeFactory.createSqlType(SqlTypeName.BIGINT);

    RelDataType structType =
        typeFactory.createStructType(Arrays.asList(intType, longType),
            Arrays.asList("x", "y"));
    RexInputRef inputRef = builder.makeInputRef(structType, 0);

    // construct RexFieldAccess fails because of negative index
    IllegalArgumentException e1 = assertThrows(IllegalArgumentException.class, () -> {
      RelDataTypeField field = new RelDataTypeFieldImpl("z", -1, intType);
      new RexFieldAccess(inputRef, field);
    });
    assertThat(e1.getMessage(),
        is("Field #-1: z INTEGER does not exist for expression $0"));

    // construct RexFieldAccess fails because of too large index
    IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> {
      RelDataTypeField field = new RelDataTypeFieldImpl("z", 2, intType);
      new RexFieldAccess(inputRef, field);
    });
    assertThat(e2.getMessage(),
        is("Field #2: z INTEGER does not exist for expression $0"));

    // construct RexFieldAccess fails because of incorrect type
    IllegalArgumentException e3 = assertThrows(IllegalArgumentException.class, () -> {
      RelDataTypeField field = new RelDataTypeFieldImpl("z", 0, longType);
      new RexFieldAccess(inputRef, field);
    });
    assertThat(e3.getMessage(),
        is("Field #0: z BIGINT does not exist for expression $0"));

    // construct RexFieldAccess successfully
    RelDataTypeField field = new RelDataTypeFieldImpl("x", 0, intType);
    RexFieldAccess fieldAccess = new RexFieldAccess(inputRef, field);
    RexChecker checker = new RexChecker(structType, () -> null, Litmus.THROW);
    assertThat(fieldAccess.accept(checker), is(true));
  }

  /** Emulate a user defined type. */
  private static class UDT extends RelDataTypeImpl {
    UDT() {
      this.digest = "(udt)NOT NULL";
    }

    @Override protected void generateTypeString(StringBuilder sb, boolean withDetail) {
      sb.append("udt");
    }
  }

  @Test void testUDTLiteralDigest() {
    RexLiteral literal = new RexLiteral(new BigDecimal(0L), new UDT(), SqlTypeName.BIGINT);

    // when the space before "NOT NULL" is missing, the digest is not correct
    // and the suffix should not be removed.
    assertThat(literal.digest, is("0L:(udt)NOT NULL"));
  }


  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5489">[CALCITE-5489]
   * RexCall to TIMESTAMP_DIFF function fails to convert a TIMESTAMP literal to
   * a org.apache.calcite.avatica.util.TimeUnit</a>. */
  @Test void testTimestampDiffCall() {
    final RexImplicationCheckerFixtures.Fixture f =
        new RexImplicationCheckerFixtures.Fixture();
    final TimestampString ts =
        TimestampString.fromCalendarFields(Util.calendar());
    final RexNode literal = f.timestampLiteral(ts);
    final RexLiteral flag = f.rexBuilder.makeFlag(TimeUnit.QUARTER);
    assertThat(
        f.rexBuilder.makeCall(SqlLibraryOperators.DATEDIFF,
            flag, literal, literal),
        notNullValue());
    assertThat(
        f.rexBuilder.makeCall(SqlStdOperatorTable.TIMESTAMP_DIFF,
            flag, literal, literal),
        notNullValue());
    assertThat(
        f.rexBuilder.makeCall(SqlLibraryOperators.TIMESTAMP_DIFF3,
            literal, literal, flag),
        notNullValue());
    assertThat(
        f.rexBuilder.makeCall(SqlLibraryOperators.TIME_DIFF,
            literal, literal, flag),
        notNullValue());
  }
}
